"""
 *
 * This file is part of Pyphantom
 *
 * Copyright (C) 2007 Nokia Corporation
 *
 * Contact: Karoliina Salminen <karoliina.t.salminen@nokia.com>
 *
 * Author(s): Henry Vieira <henry.vieira@nokia.com>
 *            Edisson Braga <edisson.braga@indt.org.br>
 *            Rodrigo Belem <rodrigo.belem@indt.org.br>
 *
 * This package is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 dated June, 1991.
 *
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this package; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 *
"""

import os
import re
import time
import codecs
import locale
import ConfigParser

class DebConfigParser(ConfigParser.SafeConfigParser):

    # hackish :-P eeheheh
    def optionxform(self, optionstr):
        return optionstr


class DebControlConfig(DebConfigParser):

    # TODO: custom fields are not implemented

    pkg_dep_fields = ['Pre-Depends', 'Depends', 'Recommends', 'Suggests',
                        'Enhances', 'Conflicts', 'Breaks', 'Replaces',
                        'Provides']

    src_dep_fields = ['Build-Depends', 'Build-Depends-Indep', 'Build-Conflicts',
                        'Build-Conflicts-Indep']

    src_general_fields = [['Source', 'Section', 'Priority', 'Maintainer'],
                            src_dep_fields, ['Standards-Version']]

    src_binary_fields = [['Package', 'Architecture', 'Section', 'Priority',
                            'Essential'], pkg_dep_fields, ['Description']]

    binary_fields = [['Package', 'Source', 'Version', 'Architecture', 
                    'Essential', 'Origin', 'Bugs', 'Maintainer',
                    'Installed-Size'], pkg_dep_fields, ['Section', 'Priority',
                    'Description']]

    # regular expression to parse the field and value
    OPTCRE = re.compile(
        r'(?P<option>[^:=\s][^:=]*)'          # very permissive!
        r'\s*(?P<vi>[:=])\s*'                 # any number of space/tab,
                                              # followed by separator
                                              # (either : or =), followed
                                              # by any # space/tab
        r'(?P<value>.*[\n]*)$',               # everything up to eol
        re.UNICODE
        )

    # regular expression to parse the package its version and relation
    DEPSRE = re.compile(
        r'(?P<package>[a-zA-Z0-9][\w\-+.]*)'
        r'\s*[\(]'
        r'(?P<relation>(<<|<=|=|>=|>>))'
        r'\s*'
        r'(?P<version>[\w\-+.:]+)'
        r'[\)]',
        re.UNICODE
        )

    def __init__(self):
        self._sections = self._packages = {}
        self._defaults = self._sources = {}
        self.filename = os.path.join('debian', 'control')

    def _read(self, fp, fpname):
        """Parse a sectioned setup file.

        The sections in setup file contains a title line at the top,
        indicated by a name in square brackets (`[]'), plus key/value
        options lines, indicated by `name: value' format lines.
        Continuations are represented by an embedded newline then
        leading whitespace.  Blank lines, lines beginning with a '#',
        and just about everything else are ignored.
        """
        cursect = None                              # None, or a dictionary
        optname = None
        #prev_optname = None
        #cur_field_list = None
        lineno = 0
        prev_line_blank = None                      # None, True or False
        e = None                                    # None, or an exception

        while True:
            line = fp.readline()
            if not line:
                break
            lineno = lineno + 1

            # continuation line?
            if line[0].isspace() and cursect is not None and optname:
                value = line.strip()
                if value:
                    # group lines without remove whitespaces and \n's
                    cursect[optname] = "%s%s" % (cursect[optname], line)
                    prev_line_blank = False
                # blank line?
                elif line.strip() == '':
                    #cursect = None
                    prev_line_blank = True

            # a section header or option header?
            else:
                mo = self.OPTCRE.match(line)
                if mo:

                    optname, vi, optval = mo.group('option', 'vi', 'value')

                    optname = optname.rstrip()

                    # Using Source as section name
                    if lineno == 1 and optname == 'Source':
                        #sectname = optname.strip()
                        cursect = self._defaults
                    # Using PackageName as section name
                    elif prev_line_blank and optname == 'Package':
                        sectname = optval.strip()

                        if sectname in self._sections:
                            raise ConfigParser.ParsingError, 'debian/control ' \
                                'has a duplicate entry for %s ' % optval.strip()
                        else:
                            cursect = {'__name__': sectname}
                            self._sections[sectname] = cursect

                    # TODO: if custom debian field is valid add it to src_dep_fields, src_general_fields
                    # or src_binary_fields depending its reading position
                    cursect[optname] = optval
                    prev_line_blank = False

                else:
                    # a non-fatal parsing error occurred.  set up the
                    # exception but keep going. the exception will be
                    # raised at the end of the file and will contain a
                    # list of all bogus lines
                    if not e:
                        e = ConfigParser.ParsingError(fpname)
                    e.append(lineno, repr(line))

        # if any parsing errors occurred, raise an exception
        if e:
            raise e

    def read(self):
        """Read and parse a filename

        Return True if the file can be read.
        """
        filename = os.path.join('debian', 'control')

        if os.path.exists(filename):
            try:
                fp = codecs.open(filename, encoding='utf8')
                self.file_lines = fp.readlines()
                fp.close()
                fp = codecs.open(filename, encoding='utf8')
            except IOError:
                raise ConfigParser.ParsingError, 'cannot read %s' % filename
            # blanking _sections _defaults
            self._sections = {}
            self._defaults = {}
            self._read(fp, filename)
            fp.close()
            return True
        else:
            raise ConfigParser.ParsingError, 'cannot read %s' % filename

    def _insert_after(self, field_list, field, desired_field):
        """
        This function insert a element in a list after a determinated element
        """
        
        try:
            index = field_list[0].index(field)
        except ValueError:
            return
        else:
            field_list.insert(index + 1, desired_field)

    def _parse_value(self, option, value):
        '''
        This function returns a list with the parsed value
        '''
        
        if option == 'Description':
            return [value]
        elif option in self.src_dep_fields or option in self.pkg_dep_fields:
            value = value.split(',')
            for i in range(len(value)):
                value[i] = value[i].strip()
                if '|' in value[i]:
                    value[i] = value[i].split('|')
                    for j in value[i]:
                        mo = self.DEPSRE.match(j)
                        if mo:
                            value[i].append((mo.group('package'),
                                            mo.group('relation'),
                                            mo.group('version')
                                            ))
                        else:
                            value[i].append((j,))
                else:
                    mo = self.DEPSRE.match(value[i])
                    if mo:
                        value[i] = [(mo.group('package'),
                                    mo.group('relation'),
                                    mo.group('version')
                                    )]
                    else:
                        print value[i]
                        value[i] = [(value[i].strip(),)]
            return value
        else:
            value = value.split(',')
            for i in range(len(value)):
                value[i] = value[i].strip()
            return value


    # TODO: this function will be used when set_data is ready
    def get_data(self, section, option):

        d = self._defaults.copy()
        try:
            d.update(self._sections[section])
        except KeyError:
            if section != DEFAULTSECT:
                raise NoSectionError(section)

        try:
            value = d[option]
        except KeyError:
            raise NoOptionError(option, section)

        return self._parse_value(option, value)


    # TODO: Define an option write order

    def _write(self, fp):
        '''Write an .ini-format representation of the configuration state.'''
        if self._defaults:
            for item in self.src_general_fields:
                for field in item:
                    if field in self._defaults.keys():
                        fp.write('%s: %s\n' % (field, self._defaults[field].strip()))
            fp.write('\n')

        for section in self._sections:
            for item in self.src_binary_fields:
                for field in item:
                    if field in self._sections[section].keys():
                        if field != '__name__':
                            fp.write('%s: %s\n' % (field, self._sections[section][field].strip()))

            fp.write('\n')

    def write(self):

        try:
            fp = codecs.open(self.filename, mode='wb', encoding='utf8')
        except IOError:
            raise ConfigParser.ParsingError, 'cannot read %s' % self.filename

        self._write(fp)

        fp.close()

class DebChangelogConfig(DebConfigParser):

    # TODO: improve the HEADRE
    # TODO: parse just the first section and add an option to parse the entire file
    HEADRE = re.compile(
        r'(?P<Source>^[-.a-zA-Z0-9_]+)'
        r'\s\('
        r'(?P<Version>[^\(\) \t]+)'
        r'\)\s'
        r'(?P<Distribution>[-+0-9a-z.\s]+)'
        r'\;\surgency='
        r'(?P<Urgency>[a-z]+$)',
        re.UNICODE
        )

    BOTTONRE = re.compile(
        r'^\s\-\-\s'
        r'(?P<Maintainer>[.\w\s\-\'\(\)]+\s<[a-zA-Z0-9._+\-]+@[a-zA-Z0-9.\-]+>)'
        r'\s\s'
        r'(?P<Date>[A-Za-z]{3}[,]\s+\d{1,2}\s[A-Za-z]{3}\s\d{4,4}\s\d{1,2}:\d\d:\d\d\s[-+]\d{4})',
        re.UNICODE
        )

    VERSIONRE = re.compile(
        r'^(((?P<head>([0-9]))(:))*)'
        r'(?P<softversion>[a-z0-9\.+]+)'
        r'-'
        r'(?P<debpkgversion>[0-9]+)'
        r'((?P<distribution>[a-z]+)*)'
        r'((?P<distropkgver>[0-9\.]+)*)',
        re.UNICODE)

    CHANGESRE = re.compile(r'^\s+[*\[][\w\s\(\)\]\+#:-]+', re.UNICODE)

    SPACES = re.compile(r'\s{4}', re.UNICODE)

    changelog_fields = ['Source', 'Version', 'Distribution', 'Urgency',
                        'Maintainer', 'Date', 'Closes', 'Changes']

    def __init__(self):
        self.file_lines = []
        self.filename = os.path.join('debian', 'changelog')
        self._sections = {}
        self._defaults = {}

    def increment(self, **kwds):
        """
        version: if not passed as argument it will receive previous version plus 1
        distribution: if not passed as argument it will receive previous distribution
        urgency: the same of above
        changes: if not passed as argument the string 'New upstream release' will be inserted
        maintainer: same as distribution
        """
        if self.sections():
 
            if 'Version' not in kwds:
                kwds['Version'] = self.get(self.sections()[0], 'Version')
                res = self.VERSIONRE.match(kwds['Version'])
                try:
                    if res.groupdict()['distropkgver']:
                        vernum = str(int(res.groupdict()['distropkgver']) + 1)
                    else:
                        vernum = str(int(res.groupdict()['debpkgversion']) + 1)
                except ValueError:
                    if res.groupdict()['distropkgver']:
                        vernum = str(float(res.groupdict()['distropkgver']) + 0.1)
                    else:
                        vernum = str(float(res.groupdict()['debpkgversion']) + 0.1)
                    
                self.set(self.sections()[0], 'Version', 
                    kwds['Version'][:len(kwds['Version']) - len(vernum)] + vernum)
            
            if 'Changes' not in kwds:
                self.set(self.sections()[0], 'Changes',
                    '  * New upstream release\n')

            if 'Urgency' not in kwds:
                self.set(self.sections()[0], 'Urgency', 'low')

            # setting the current date
            curlocale = locale.getdefaultlocale()
            locale.setlocale(locale.LC_ALL, ('en_US', 'UTF8'))
            self.set(self.sections()[0], 'Date', time.strftime('%a, %d %b %Y %H:%M:%S %z', time.gmtime()))
            locale.setlocale(locale.LC_ALL, curlocale)
            
            self._write()

    def _delete_current_entry(self):

        while self.file_lines:

            self.file_lines.pop(0) 

            if self.file_lines:
                if self.HEADRE.match(self.file_lines[0]):
                    break

    def _write(self):
        
        try:
            fp = codecs.open(self.filename, mode='wb', encoding='utf8')
        except IOError:
            raise ConfigParser.ParsingError, 'cannot read %s' % self.filename

        entry = \
            '%s (%s) %s; urgency=%s\n' \
            '\n' \
            '%s' \
            '\n' \
            ' -- %s  %s\n' \
            '\n' % (
                self.get(self.sections()[0], 'Source'),
                self.get(self.sections()[0], 'Version'),
                self.get(self.sections()[0], 'Distribution'),
                self.get(self.sections()[0], 'Urgency'),
                self.get(self.sections()[0], 'Changes'),
                self.get(self.sections()[0], 'Maintainer'),
                self.get(self.sections()[0], 'Date'))

        entry.splitlines(1).reverse()
        new_entry = entry.splitlines(1)
        new_entry.reverse()
        for item in new_entry:
            self.file_lines.insert(0, item)

        fp.writelines(self.file_lines)
        fp.close()

        self.read()

    def write(self):
        
        self._delete_current_entry()
        self._write()

    # TODO: order sections by entry
    def _read(self, fp, fpname):
        """Parse a sectioned setup file.

        The sections in setup file contains a title line at the top,
        indicated by a name in square brackets (`[]'), plus key/value
        options lines, indicated by `name: value' format lines.
        Continuations are represented by an embedded newline then
        leading whitespace.  Blank lines, lines beginning with a '#',
        and just about everything else are ignored.
        """
        cursect = None                            # None, or a dictionary
        optname = None
        headcount = 0
        lineno = 0
        e = None                                  # None, or an exception
        while True:
            line = fp.readline()
            if not line:
                break
            lineno = lineno + 1
            # comment?
            if line[0] in '#;':
                # Changed to raise an error
                raise ConfigParser.ParsingError
            # continuation line?
            # Changed to not remove whitespaces
            if self.SPACES.match(line) and cursect is not None and optname == 'Changes':
                value = line.strip()
                if value:
                    cursect[optname] = "%s%s" % (cursect[optname], line)
            # blank line?
            elif line.strip() == '':
                # Doing nothing
                pass
            # a section header or option header?
            else:

                # is it a section header?
                if self.HEADRE.match(line):
                    mo = self.HEADRE.match(line)
                    
                    # check if only the first changelog entry should be read
                    if headcount > 0:
                        break

                    headcount += 1
                    # 
                    sectname = mo.group('Version')

                    if sectname not in self._sections:
                        cursect = {'__name__': sectname}
                        self._sections[sectname] = cursect
                    else:
                        cursect = self._sections[sectname]
                        
                    for optname in ['Source', 'Version', 'Distribution', 'Urgency']:
                        optval = mo.group(optname)
                        cursect[optname] = optval

                elif self.CHANGESRE.match(line):
                    mo = self.CHANGESRE.match(line)
                    optname = 'Changes'
                    optval = line
                    cursect[optname] = optval

                elif self.BOTTONRE.match(line):
                    mo = self.BOTTONRE.match(line)

                    for optname in ['Maintainer', 'Date']:
                        optval = mo.group(optname)
                        cursect[optname] = optval

                else:
                    # a non-fatal parsing error occurred.  set up the
                    # exception but keep going. the exception will be
                    # raised at the end of the file and will contain a
                    # list of all bogus lines
                    if not e:
                        e = ConfigParser.ParsingError(fpname)
                    e.append(lineno, repr(line))
                    print e
        # if any parsing errors occurred, raise an exception
        if e:
            raise e

    def read(self):
        """Read and parse a filename

        Return True if the file can be read.
        """

        if os.path.exists(self.filename):
            try:
                fp = codecs.open(self.filename, encoding='utf8')
                self.file_lines = fp.readlines()
                fp.close()
                fp = codecs.open(self.filename, encoding='utf8')
            except IOError:
                raise ConfigParser.ParsingError, 'cannot read %s' % self.filename

            # blanking _sections _defaults
            self._sections = {}
            self._defaults = {}
            self._read(fp, self.filename)
            fp.close()
            return True
        else:
            raise ConfigParser.ParsingError, 'cannot read %s' % self.filename

    def _versioncmp(self, x, y):
        pass
        

class GenFiles(object):

    def __init__(self, control, changelog):
        self.control = control
        self.changelog = changelog

    def gencontrol(self, package, dst):
        """
        Create the binary control from the source control and changelog
        """
        # TODO: parse the user-defined fields 
        bcontrol_fd = codecs.open(dst, 'w')

        for item in self.control.binary_fields:
            for field in item:
                if self.control.has_option('DEFAULT', field):
                    bcontrol_fd.write('%s: %s\n' % (field, self.control.get('DEFAULT', field).strip()))
                elif self.control.has_option(package, field):
                    bcontrol_fd.write('%s: %s\n' % (field, self.control.get(package, field).strip()))
                elif field == 'Version':
                    bcontrol_fd.write('%s: %s\n' % ('Version', self.changelog.sections()[0].strip()))

        bcontrol_fd.close()
    
    def genchanges(self):
        pass
